﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Data.Linq.Mapping;
using System.ComponentModel;
using System.Data;
using StockholmsStad.Data.Interfaces;

namespace StockholmsStad.Data
{

    /// <summary>
    /// Implementation av interfacet IDataContextGeneric
    /// </summary>
    public class DataContextGeneric : IDataContextGeneric
    {
        #region IDBContextGeneric Members

        public T SelectByPrimaryKey<T>(int id) where T : class
        {
            try
            {
                var dbCtx = new StockholmsStadDataContext(IsolationLevel.RepeatableRead);
                // hämta tabellen för typen T
                var table = dbCtx.GetTable<T>();

                // hämta metamodellen (databas -> domänobjekt)
                MetaModel modelMap = table.Context.Mapping;

                // hämta datamedlemmarna
                ReadOnlyCollection<MetaDataMember> dataMembers = modelMap.GetMetaType(typeof (T)).DataMembers;

                // hitta primärnyckeln
                // by checking for IsPrimaryKey
                string primaryKey = (dataMembers.Single<MetaDataMember>(m => m.IsPrimaryKey)).Name;

                // hämta aktuellt objekt 
                return table.SingleOrDefault<T>(
                    delegate(T t)
                        {
                            String memberId =
                                t.GetType().GetProperty(primaryKey).GetValue(t, null).
                                    ToString();
                            return memberId.ToString() == id.ToString();
                        });
            }
            catch (Exception)
            {
                throw;
            }
        }


        public List<T> SelectAll<T>() where T : class
        {
            try
            {
                var dbCtx = new StockholmsStadDataContext(IsolationLevel.RepeatableRead);
                // hämta aktuell tabell och lista allt innehåll
                var table = dbCtx.GetTable<T>().ToList<T>();
                return table;
            }
            catch (Exception)
            {
                throw;
            }
        }

        public Type GetPrimaryKeyType<T>() where T : class
        {
            try
            {
                var dbCtx = new StockholmsStadDataContext(IsolationLevel.RepeatableRead);
                var table = dbCtx.GetTable<T>();
                MetaModel modelMapping = table.Context.Mapping;
                ReadOnlyCollection<MetaDataMember> dataMembers = modelMapping.GetMetaType(typeof (T)).DataMembers;

                // hitta primärnyckeln och returnera dess typ
                return (dataMembers.Single<MetaDataMember>(m => m.IsPrimaryKey)).Type;
            }
            catch (Exception)
            {
                throw;
            }
        }

        public string GetPrimaryKeyName<T>() where T : class
        {
            try
            {
                var dbCtx = new StockholmsStadDataContext(IsolationLevel.RepeatableRead);
                var table = dbCtx.GetTable<T>();
                MetaModel modelMapping = table.Context.Mapping;
                ReadOnlyCollection<MetaDataMember> dataMembers = modelMapping.GetMetaType(typeof (T)).DataMembers;

                // hitta primärnyckeln och returnera dess namn
                return (dataMembers.Single<MetaDataMember>(m => m.IsPrimaryKey)).Name;
            }
            catch (Exception)
            {
                throw;
            }
        }

        public void Insert<T>(T item) where T : class
        {
            try
            {
                // hämta tabell och lägg till det nya objektet och submitta ändringarna
                var dbCtx = new StockholmsStadDataContext(IsolationLevel.RepeatableRead);
                var table = dbCtx.GetTable<T>();
                table.InsertOnSubmit(item);
                dbCtx.SubmitChanges();
            }
            catch (Exception)
            {
                throw;
            }
        }

        public void Update<T>(T item) where T : class
        {
            try
            {
                // skapa ny instans
                Object newObj = Activator.CreateInstance(typeof (T), new object[0]);
                PropertyDescriptorCollection originalProps = TypeDescriptor.GetProperties(item);

                // se till att det nya objektet matchar befintligt
                foreach (PropertyDescriptor currentProp in originalProps)
                {
                    if (currentProp.Attributes[typeof (System.Data.Linq.Mapping.ColumnAttribute)] != null)
                    {
                        object val = currentProp.GetValue(item);
                        currentProp.SetValue(newObj, val);
                    }
                }

                // submitta ändringarna
                var dbCtx = new StockholmsStadDataContext(IsolationLevel.RepeatableRead);
                var table = dbCtx.GetTable<T>();
                table.Attach((T) newObj, true);
                dbCtx.SubmitChanges();
            }
            catch (Exception)
            {
                throw;
            }
        }

        public void Remove<T>(T item) where T : class
        {
            try
            {
                Type tType = item.GetType();
                Object newObj = Activator.CreateInstance(tType, new object[0]);

                // hämta egenskaperna för det bifogade objektet
                PropertyDescriptorCollection originalProps = TypeDescriptor.GetProperties(item);

                // kopiera objektet till det nya objektet med samma typ - detta ger oss ett objekt
                // som inte är kopplat till någon existerande datacontext
                foreach (PropertyDescriptor currentProp in originalProps)
                {
                    if (currentProp.Attributes[typeof (System.Data.Linq.Mapping.ColumnAttribute)] != null)
                    {
                        object val = currentProp.GetValue(item);
                        currentProp.SetValue(newObj, val);
                    }
                }

                // linq kräver att man attachar frånkopplat data (som i detta fall) till tabellen, sen tar bort det 
                // och submittar ändringarna
                var dbCtx = new StockholmsStadDataContext(IsolationLevel.RepeatableRead);
                var table = dbCtx.GetTable<T>();
                table.Attach((T) newObj, true);
                table.DeleteOnSubmit((T) newObj);
                dbCtx.SubmitChanges();
            }
            catch (Exception)
            {
                throw;
            }
        }

        #endregion
    }
}
